<!--
 Copyright (C) 2010 Software Languages Lab, Vrije Universiteit Brussel

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
-->

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">

<html lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>Proxy tracing test</title>
	<meta name="author" content="Tom Van Cutsem">
</head>

<script>
function print(text) {
  var div = document.createElement("div");
  var txt = document.createTextNode(text);
  div.appendChild(txt);
  document.getElementById('report').appendChild(div);
}

// create a no-op forwarding handler to obj
function createForwardingHandler(obj) {
  return {
	  getOwnPropertyDescriptor: function(name) {
	    var desc = Object.getOwnPropertyDescriptor(obj);
	    desc.configurable = true;
	    return desc;
	  },
	  getPropertyDescriptor: function(name) {
	    var desc = Object.getPropertyDescriptor(obj);
	    desc.configurable = true;
	    return desc;
	  },
	  getOwnPropertyNames: function() {
	    return Object.getOwnPropertyNames(obj);
	  },
	  defineProperty: function(name, desc) {
	    return Object.defineProperty(obj, name, desc);
	  },
	  'delete': function(name) { return delete obj[name]; },
	  fix: function() {
	    // As long as obj is not frozen, the proxy won't allow itself to be fixed
	    // if (!Object.isFrozen(obj)) // FIXME: not yet implemented
	    //     return undefined;
	    // return Object.getOwnProperties(obj); // FIXME: not yet implemented
	    var props = {};
	    for (x in obj)
		    props[x] = Object.getOwnPropertyDescriptor(obj, x);
	    return props;
	  },
   	has: function(name) { return name in obj; },
	  hasOwn: function(name) { return ({}).hasOwnProperty.call(obj, name); },
	  get: function(receiver, name) { return obj[name]; },
	  set: function(receiver, name, val) { obj[name] = val; return true; },
	       // bad behavior when set fails in non-strict mode
	  enumerate: function() {
	    var result = [];
	    for (name in obj) { result.push(name); };
	    return result;
	  },
	  enumerateOwn: function() { return Object.keys(obj); }
  };
};

// createTracer returnes a tuple { proxy, log }
// all operations performed on proxy are appended to the log
// the log is a simple array of 'entry' objects, where entries are of the form:
// { op: <name of applied operation>,
//   args: <array of arguments of the operation>,
//   result: <return value of the operation applied to the target> }
//
// The log can be printed by invoking printTrace(log)
var tracerId = 0;
function createTracer(target) {
  var log = [];
  log.id = '[tracer '+(tracerId++)+"]";
    
  var fwdingHandler = createForwardingHandler(target);
  var loggingHandler = {
    get: function(rcvr, trapName) {
      return function(var_args) {
        if (trapName === 'get' && arguments[1] === 'toString') {
          // trap proxy.toString() to avoid infinite loop:
          // printTrace iterates over log and calls toString on all arguments
          // if toString itself would be logged, new entries would appear in the log
          // while iterating over it, ad infinitum
          return target.toString;
        }
        
        var entry = {
          op: trapName,
          args: Array.prototype.slice.call(arguments, 0),
          result: fwdingHandler[trapName].apply(fwdingHandler, arguments)
        };
        log.push(entry);
        return entry.result;
      }
    }
  };
  var handlerProxy = Proxy.create(loggingHandler);
  var proxy;
  if (typeof target === 'function') {
    function callTrap(var_args) {
      var entry = {
        op: 'call',
        args: Array.prototype.slice.call(arguments, 0),
        result: Function.prototype.apply.call(target, this, arguments)
      };
      log.push(entry);
      return entry.result;
    };
    function consTrap(var_args) {
      var entry = {
        op: 'construct',
        args: Array.prototype.slice.call(arguments, 0),
        result: new target() // FIXME: need Function.prototype.bind to write a var-args 'new'
      };
      log.push(entry);
      return entry.result;
    };
    proxy = Proxy.createFunction(handlerProxy, callTrap, consTrap);
  } else {
    proxy = Proxy.create(handlerProxy, Object.getPrototypeOf(target));
  }
  return {proxy: proxy, log: log };
}

function printTrace(log) {
  for (var i = 0; i < log.length; i++) {
    var entry = log[i];
    var args = entry.args;
    var argstring = "(";
    for (var j = 0; j < args.length - 1; j++) {
      argstring += args[j].toString() + ",";
    }
    if (args.length > 0) {
      argstring += args[args.length-1].toString();
    }
    argstring += ")";
    print(log.id + ": "+ entry.op + argstring + ' returned '+entry.result);
  }
}

function runTest() { 
  // Object proxies
  
  var tuple = createTracer({toString: function() { return 'test1' }});
  var proxy = tuple.proxy;
  
  proxy.foo;
  proxy.foo = 42;
  proxy instanceof Object;
  proxy.foo;
  'foo' in proxy;
  delete proxy.foo;
  Object.keys(proxy);
  proxy.toString();
  
  printTrace(tuple.log);
  
  // Function proxies
  
  function f(x) { return x; };
  var tuple2 = createTracer(f);
  var fproxy = tuple2.proxy;
  
  fproxy.foo;
  fproxy.foo = 42;
  fproxy instanceof Function;
  fproxy.foo;
  'foo' in fproxy;
  delete fproxy.foo;
  Object.keys(fproxy);
  
  // FIXME: fproxy.toString(); fails with error 'Function.prototype.toString called on incompatible function'
  // it appears the built-in toString cannot cope with function proxies
  // fproxy.toString();
  // Function.prototype.toString.call(fproxy);
  
  fproxy(5);
  
  // FIXME: can't 'new' function proxies without Function.prototype.bind to deal with variable arguments
  // new fproxy(5);
  
  // FIXME: can't print function proxy trace as long as function proxies cannot be toString-ed
  //printTrace(tuple2.log);
  print('done');
}
</script>

<body>
  <button onClick="runTest()">Run Test</button>
  <div id="report"></div>
</body>
</html>
